反射三:类内部信息获取

相关文章:
Java Relfect
Java RelfectUtils
反射一:基本类周边信息获取
反射二:泛型相关周边信息获取
反射三:类内部信息获取

上两篇文章中,给大家讲了,有关类周边信息的获取方法,这篇文章中我们将深入到类的内部,看看类的构造函数,内部变量,函数等信息的获取方法。相比而言,这篇更重要。

一、构造函数相关获取


1、获取构造函数

要获取类的构造函数,有下面几种方法:

1
2
3
4
5
6
7
// 获取 public 类型的构造函数
Constructor<?>[] getConstructors();
Constructor<T> getConstructor(Class<?>... parameterTypes);
// 获取所有类型的构造函数
Constructor<?>[] getDeclaredConstructors();
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

这四种方法中,getConstructors 与 getConstructor 获取的是声明为 public 的构造函数。无法得到声明为 protected、private 的构造函数。而加有 Declared(声明)的两个函数 getDeclaredConstructors、getDeclaredConstructor 能获取所有声明的构造函数,无论它是 public、protected 还是 private 类型,都能获取到。所以这两个函数较为常用。

下面我们举个例子来看下 getDeclaredConstructors 和 getDeclaredConstructor 的用法。

1
Constructor<?>[] getDeclaredConstructors();

首先,构造一个类 Person:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Person {
private int age;
private String name;
public Person() {
}
private Person(int age, String name) {
this.age = age;
this.name = name;
}
private Person(Integer age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

在 Person 类中,目前我们只关心构造函数,所以我们声明了三个构造函数,有一个无参数的构造函数,和两个有参数的构造函数。我们把无参的构造函数声明为 public,另外两个都声明为 private。在上面我们说过,在四个获取构造函数的方法中,只有具有 Declared 的两个 getDeclaredXXX() 的函数才能获取类中所有的函数。而另外两个只能获取声明为 public 的函数。下面我们就利用一个例子来获取Person类的构造函数:

1
2
3
4
5
6
7
8
9
10
// 1、枚举
Class<?> clazz = Person.class;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor item:constructors){
Log.d(TAG,"枚举到的构造函数:"+item.toString());
}
// 2、根据类型,获取指定的构造的构造函数
Constructor<?> constructor = clazz.getDeclaredConstructor(Integer.class, String.class);
Log.d(TAG, "指定参数得到的构造函数:"+constructor.toString());

结果如下:

使用根据类型获取指定的构造的构造函数时,有两点要非常注意:

  • parameterTypes 是数据类型的 Class 对象,如果是原始类型,那么应该对应的是 int.class、double.class(原始类型也是有 class 对象的)。
  • parameterTypes 一定要与要得到的构造函数中的参数声明顺序、类型及个数要完全匹配,如果多一个、少一个或类型不匹配也是找不到的。这一点是极其要注意的,getDeclaredConstructor 不是模糊匹配而是精确匹配。

例如上述两个私有构造函数,第一个构造函数的 age 类型是原始类型 int,第二个构造函数的 age 参数类型是 Integer。那下列语句要匹配哪个构造函数呢?

1
clazz.getDeclaredConstructor(Integer.class, String.class);

由于这里是严格匹配,所以这里必然匹配的是 private Person(Integer age, String name) 函数,如果我们要匹配 private Person(int age, String name) 要怎么办呢?那获取构造函数的方法就要这么写了:

1
clazz.getDeclaredConstructor(int.class, String.class);

这里要非常提醒大家的是,不光派生自 Object 的类具有 Class 对象,原始的数据类型也是具有 Class 对象的。

2、Constructor 构造实例

利用 Constructor 的对象来构造实例时,主要是使用的是 newInstance 方法:

1
public T newInstance(Object... args);

可以看到 newInstance 有可以传入可变长参数,值得非常注意的是,传入的参数类型、顺序及个数都必须与当前的 Constructor 对象一一对应,不然就会报下面的错误:

1
java.lang.IllegalArgumentException: Wrong number of arguments; expected 2, got 0

继续使用上述 Person 类,下面来看看,如何使用 newInstance 来构造 Person 的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(Integer.class, String.class);
constructor.setAccessible(true);
// 构造实例一
Person person1 = (Person) constructor.newInstance(new Integer(30), new String("xian"));
Log.d(TAG, "构造的参数为:" + person1.getName() + " " + person1.getAge());
// 构造实例二
Person person2 = (Person) constructor.newInstance(50,"xiao");
Log.d(TAG,"构造的参数为:"+person2.getName() + " "+ person2.getAge());
// 构造实例三
Person person3 = (Person) constructor.newInstance();
person3.setAge(30);
person3.setName("xian");
Log.d(TAG,"构造的参数为:"+person3.getName() + " "+ person3.getAge());

结果如下:

这段代码总共分为四部分:
第一部分:获取 Constructor 对象

1
2
3
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(Integer.class, String.class);
constructor.setAccessible(true);

首先,通过 getDeclaredConstructor(Integer.class, String.class) 获取指定的构造函数,要得到的这个构造函数,第一个参数是 Integer 类型,第二个参数是 String 类型。所以匹配的是下面这个构造函数:

1
2
3
4
private Person(Integer age, String name) {
this.age = age;
this.name = name;
}

最后一句利用的是 Constructor 的函数 setAccessible,它完整的声明是:

1
void setAccessible(boolean flag)

它代表的含义是,是否将任何函数或字段设置为可访问的。如果设置为 true,就不管这个函数或者字段是声明为 private 还是 public,都是可以访问的,默认情况下是 false,即只有 public 类型是可访问的。如果没有设置 setAccessible(true) 的话,在使用 protected 或者 private 构造函数创建实例时,会提示访问拒绝,如下所示:

1
2
3
4
java.lang.IllegalAccessException: Class java.lang.Class<com.xxt.xtest.TestActivity>
cannot access private method void
com.xxt.xtest.Person.<init>(java.lang.Integer, java.lang.String)
of class java.lang.Class<com.xxt.xtest.Person>

在反射中,总共有三种类型具有 setAccessible(boolean flag) 函数:Constructor、Field、Method;分别对应构造函数,成员变量和成员函数。我们知道在一个类中,也就只有构造函数,成员变量和成员函数这三部分,而且这三部分都可能会被设置为 private 和 protected,所以它们三个都会具有设置是否可访问的 setAccessible(boolean flag) 函数。

第二部分:使用标准的 Integer, String 构造实例

1
2
3
// 构造实例一
Person person1 = (Person) constructor.newInstance(new Integer(30), new String("xian"));
Log.d(TAG, "构造的参数为:" + person1.getName() + " " + person1.getAge());

在这个利用中,我们向 newInstance() 函数中传递的两个参数是严格遵守 constructor 所对应的构造函数的,即第一个参数是 Integer 类型,第二个参数是 String 类型。然后将 constructor.newInstance 的返回值强转成 Person 的实例。最后通过 person 的函数 person1.getName() 和 person1.getAge() 获取它的内容。

第三部分:使非标准,但可转换的参数来构造实例

1
2
3
// 构造实例二
Person person2 = (Person) constructor.newInstance(50,"xiao");
Log.d(TAG,"构造的参数为:"+person2.getName() + " "+ person2.getAge());

在这个构造方法中,我们传递的两个参数都不是标准的 Integer 和 String 类型。但第一个参数 50 是可以强转成 Integer 类型的,而第二个字符串,也是可以强转为 String 类型的。所以对于可以强转成为参数类型的数值,也是可以成功接受的。所以从结果也可以看出,我们可以成功得到 person2 中的值。

第四部分:如果不按规则传递参数会怎样
我们知道,我们得到的 constructor 对应的构造函数是:private Person(Integer age, String name),所在我们在 newInstance 的时候,必须声明这个 Constructor 时的参数类型,顺序及个数一个个传进去参数的对应值,如果我们不传、少传或者多传,结果会怎样呢?我们来看看第三个构造实例:

1
2
3
4
5
// 构造实例三
Person person3 = (Person) constructor.newInstance();
person3.setAge(30);
person3.setName("xian");
Log.d(TAG,"构造的参数为:"+person3.getName() + " "+ person3.getAge());

在这个实例中,我们没有按照 constructor 的参数顺序去填对应的值,而是压根没有填任何的值。如果我们 constructor 对应的构造函数是下面这个没有参数的构造函数的话,这么做是允许的。

1
2
public Person() {
}

而我们这里的 constructor 对应的构造函数却是:private Person(Integer age, String name),即便我们后面利用 set 方法重新设置进了各种参数,但这是没用的,因为在程序执行到 constructor.newInstance(); 的时候就已经报错了。错误信息为:参数个数不对。

1
java.lang.IllegalArgumentException: Wrong number of arguments; expected 2, got 0

3、Constructor 获取参数类型

Constructor 中获取参数类型的主要有两个方法:

1
2
3
4
// 用于解析一般函数
Class<?>[] getParameterTypes();
// 用于解析泛型对象
Type[] getGenericParameterTypes();

此处举例说明 getParameterTypes() 。下面的程序将实现,分别打印出 Person.class 中所有构造函数的参数类型列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
Class<?> clazz = Person.class;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> c : constructors){
c.setAccessible(true);
Class<?>[] types = c.getParameterTypes();
StringBuilder builder = new StringBuilder("获取参数类型为:");
for(Class t : types){
builder.append(t.getName());
builder.append(" ");
}
Log.d(TAG,builder.toString());
}

执行结果如下:

4、Constructor 获取构造函数的访问修饰符

示例函数:打印出每个构造函数的访问修饰符。

1
2
3
4
5
6
7
Class<?> clazz = Person.class;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> c:constructors) {
c.setAccessible(true);
int modifier = c.getModifiers();
Log.d(TAG,"一个访问修饰符为:" + Modifier.toString(modifier));
}

结果如下:

5、Constructor getDeclaringClass()

方法声明如下:

1
Class<T> getDeclaringClass();

该函数用于得到声明 Constructor 的类的 Class 对象。用个例子来说明下:

1
2
3
4
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getDeclaredConstructor();
Class<?> declareClassClazz = constructor.getDeclaringClass();
Log.d(TAG,declareClassClazz.getName());

结果如下:

1
2020-03-08 18:48:00.432 10977-10977/com.xxt.xtest D/xian: com.xxt.xtest.Person

二、成员变量的获取与设置


1、获取 Field 对象

类中的信息,无外乎有三种:构造函数、成员变量和成员函数。类中成员变量相关的信息都保存在 Filed 类型的对象里。Class 中对 Field 对象的获取有下面四个函数:

1
2
3
4
5
6
// 仅能获取声明类型为 public 的成员变量
Field[] getFields();
Field getField(String name);
// 可以获取全部的成员变量
Field[] getDeclaredFields();
Field getDeclaredField(String name)

1.1、getDeclaredFields()

使用上面的 Person.class 进行测试:

1
2
3
4
5
6
7
public class Person {
private int age;
private String name;
public Person() {
}
...
}

在 Person 类中,我们有两个成员变量,一个是 int age,另一个是 String name,非常注意的是这两个变量全部都被声明为 private。一方面,我们只有利用 getDeclaredXXX() 系列函数才能得到他们的Field;另一方面,如果要访问每个成员变量所对应 Field 对象的信息,就必须设置 field.setAccessible(true); 。

在 Constructor 中,我们已经提到在类中的三部分:构造函数、成员变量、成员函数,他们都可能会被设置为 private 或 protected 类型,所以为了能让用户访问 private 或 protected 类型的对象,在它们三个所对应的类型 Constructor、Field、Method 中都有实现 setAccessible(boolean flag) 函数。意义与都完全一样:表示是否将当前对象设置成为可访问的。默认是 false,即如果当前对象的声明类型为 private 或 protected 就无法访问。

示例代码:

1
2
3
4
5
6
7
Class<?> clazz = Person.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Class<?> type = field.getType();
Log.d(TAG,"枚举到的 field: " + type.getName() + " " + field.getName());
}

结果为:

1
2
3
4
.../com.xxt.xtest D/xian: 枚举到的 field: int age
.../com.xxt.xtest D/xian: 枚举到的 field: java.lang.String name
.../com.xxt.xtest D/xian: 枚举到的 field: com.android.tools.ir.runtime.IncrementalChange $change
.../com.xxt.xtest D/xian: 枚举到的 field: long serialVersionUID

1.2、getDeclaredField(String name)
1
2
3
4
5
Class<?> clazz = Person.class;
Field field = clazz.getDeclaredField("age");
field.setAccessible(true);
Class<?> type = field.getType();
Log.d(TAG,"age field: " + type.getName() + " " + field.getName());

结果为:

1
.../com.xxt.xtest D/xian: age field: int age

2、Field:get & set

2.1、get() & set()

当获取或设置指定类对象中某变量的值时,可以使用 Field 中的 get、set 方法,声明如下:

1
2
void set(Object object, Object value)
Object get(Object object)

示例代码:

1
2
3
4
5
6
7
8
9
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getConstructor();
Person person = (Person)constructor.newInstance();
Field fName = clazz.getDeclaredField("name");
fName.setAccessible(true);
fName.set(person, "xian");
String val = (String)fName.get(person);
Log.d(TAG, "fieldName: " + val + ", personName: " + person.getName());

结果如下:

1
.../com.xxt.xtest D/xian: fieldName: xian, personName: xian

大家注意,在 set 时,第一个参数是要设置的 Person 的实例,第二个参数是这个 Field 所对应的值。在 get 时,传递的参数是当前要获取的 Person 实例,得到的是当前实例中对应成员变量的值。

2.2、get 系列函数与 set 系列函数

再回过头来看上面的 get 与 set 函数:

1
2
void set(Object object, Object value)
Object get(Object object)

在 set 中,要传入所 Field 的值必须是派生自 Object 类型的,也就规定这个 Field 的所对应的成员变量类型就必须是派生自 Object 的的类型,比如 Integer、String 等。

那么问题就来了,那我们的成员变量的类型,并不一定是派生自 Object 的类型啊,也可能是原始的数据类型,比如 int、double、char、byte 等,那这些类型的成员变量的值要怎么设置与获取呢?
为了解决这个问题,Java 的开发者们额外开发了几对 get 与 set 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 设置与获取 int 类型的值
void setInt(Object object, int value)
int getInt(Object object)
// 设置与获取 double 类型的值
void setDouble(Object object, double value)
double getDouble(Object object)
// 设置与获取 float 类型的值
void setFloat(Object object, float value)
float getFloat(Object object)
// 设置与获取 bool 类型的值
void setBoolean(Object object, boolean value)
boolean getBoolean(Object object)
// 设置与获取 short 类型的值
void setShort(Object object, short value)
short getShort(Object object)
// 设置与获取 long 类型的值
void setLong(Object object, long value)
long getLong(Object object)
// 设置与获取 byte 类型的值
void setByte(Object object, byte value)
byte getByte(Object object)
// 设置与获取 char 类型的值
void setChar(Object object, char value)
char getChar(Object object)

示例代码:
···
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getConstructor();
Person person = (Person)constructor.newInstance();

Field fAge = clazz.getDeclaredField(“age”);
fAge.setAccessible(true);
fAge.setInt(person, 20);
Log.d(TAG, “fieldAge: “ + fAge.getInt(person) + “, personAge:” + person.getAge());
···
结果为:

1
.../com.xxt.xtest D/xian: fieldAge: 20, personAge:20

3、Field 之 isEnumConstant()

1
boolean isEnumConstant()

这个函数用于判断当前 Field 是否为枚举常量。举例:

1
2
3
4
5
6
7
8
public class Person {
public static enum COLOR{WHITE,BLACK,YELLOW}
private int age;
private String name;
private COLOR color;
...
}

测试代码:

1
2
3
4
5
6
7
8
9
Class<?> clazz2 = Person.COLOR.class;
Field field = clazz2.getDeclaredField("WHITE");
Log.d(TAG,"COLOR.WHITE 是否是枚举常量:" + field.isEnumConstant());
Class<?> clazz = Person.class;
Field fColor = clazz.getDeclaredField("color");
fColor.setAccessible(true);
boolean isEnum = fColor.isEnumConstant();
Log.d(TAG,"color 是否是枚举常量:" + isEnum);

结果为:

1
2
2020-03-08 19:30:42.298 15040-15040/com.xxt.xtest D/xian: COLOR.WHITE 是否是枚举常量:true
2020-03-08 19:30:42.298 15040-15040/com.xxt.xtest D/xian: color 是否是枚举常量:false

我们从结果中可以看到,首先 COLOR 枚举类中的 WHITE 是一个枚举常量,而利用 COLOR 定义的 color 变量不是枚举常量。

4、Field Modifiers getDeclaringClass()

1
2
3
4
// 获取该成员变量所对应的访问修饰符组所对应的 Int 数字
int getModifiers()
// 获取声明该变量的类
Class<?> getDeclaringClass()

getModifiers():获取该 Field 所对应的成员变量的访问修饰符。
getDeclaringClass():获取声明该成员变量的类的 Class 对象。

1
2
3
4
Class<?> clazz = Person.class;
Field fAge = clazz.getDeclaredField("age");
Class<?> declareClazz = fAge.getDeclaringClass();
Log.d(TAG, declareClazz.toString());

结果为:

1
.../com.xxt.xtest D/xian: class com.xxt.xtest.Person

三、成员函数的获取与设置


1、获取 Method 对象

与成员变量对应的类型为 Field 类似,类中的方法对应的类型是 Method。获取指定类中的成员函数的方法有下面四种方法:

1
2
3
4
5
Method[] getMethods()
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, Class<?>... parameterTypes)

与上面所有的有 Declared 系列的函数组一样,getMethods() 和 getMethod() 只能获取声明为 public 的函数。而 getDeclaredMethods()、getDeclaredMethod() 可以得到所有的函数,无论声明为 public、protected 还是 private。

1.1、getDeclaredMethods()

该函数能够获取类中所有声明的函数(不包括构造函数),举个例子来看他使用方法:

1
2
3
4
5
Class<?> clazz = Person.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
Log.d(TAG, "枚举到的方法:" + m.toString());
}

结果为:

1.2、getDeclaredMethod(String, Class<?>…)
1
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
  • name:表示要获取 Method 对象的成员函数名。
  • parameterTypes:需要匹配的成员函数需要传入的参数的类型,这里需要非常注意,这里的 getDeclaredMethod 是精确匹配的,如果我们传入的函数名以及参数类型、顺序、个数与该函数声明不一致的话,就会报错。

值得非常注意的是,当我们要找的方法所对应的参数类型是原始数据类型,比如 Int、double 等,它们所对应的 Class 对象为 int.class、double.class 即可;所以原始类型也是有 Class对象的。

示例代码:

1
2
3
Class<?> clazz = Person.class;
Method method = clazz.getDeclaredMethod("setName", String.class);
Log.d(TAG, "方法:" + method.toString());

结果为:

1
.../com.xxt.xtest D/xian: 方法:public void com.xxt.xtest.Person.setName(java.lang.String)

2、Method Invoke

Invoke 函数无疑是 Method 类中最重要的方法,它的功能是用于执行 Method 对象所对应的函数。完整的声明如下:

1
Object invoke(Object receiver, Object... args)

  • Object receiver:指要执行该函数的类对象,比如我们的 Person 类的某一实例。
  • Object… args:可变长参数,用于传入该函数声明中的参数所对应的值的列表。
  • 返回值:Object,将函数的结果返回,Object 对象就是执行此函数后返回的结果。

我们在 Person 中单独再加一个函数:

1
2
3
4
5
6
7
8
public class Person {
...
public Boolean testInvoke(Integer age, String name) {
Log.d("xian","得到参数 age: " + age + ", name: "+name);
return true;
}
}

示例代码:

1
2
3
4
5
6
Class<?> clazz = Person.class;
Person person = new Person();
Method method = clazz.getDeclaredMethod("testInvoke", Integer.class, String.class);
method.setAccessible(true);
Boolean result = (Boolean)method.invoke(person, 25, "I m xian");
Log.d(TAG,"执行结果: "+result);

结果为:

1
2
2020-03-08 19:53:51.572 16600-16600/com.xxt.xtest D/xian: 得到参数 age: 25, name: I m xian
2020-03-08 19:53:51.573 16600-16600/com.xxt.xtest D/xian: 执行结果: true

这里总结一下,从 Field、Method 中都可以看出,Field、Method 的对象对应的不是哪个类实例中变量,而是类中变量的对象。也就是说,Field、Method 对象只有一个,是在编译时,保存在 Class 中的。而 Person 的实例却可以有很多个,如果要改变哪个 Person 实例中成员变量的值,就必须将该 Person 实例传入 Field 对象的参数中,让 Field 对象设置。

3、Method 获取参数类型

与其它类的获取参数类型一样,这里同样针对普通变量和泛型有两种获取参数类型的方法:

1
2
3
4
// 用于获取普通的参数类型
Class<?>[] getParameterTypes()
// 用于获取带有泛型参数的类型
Type[] getGenericParameterTypes()

示例代码:

1
2
3
4
5
6
Class<?> clazz = Person.class;
Method method = clazz.getDeclaredMethod("testInvoke", Integer.class, String.class);
Class<?>[] params = method.getParameterTypes();
for (Class c : params){
Log.d(TAG,"枚举到参数类型:"+c.getName());
}

结果为:

1
2
2020-03-08 19:58:29.982 16600-16600/com.xxt.xtest D/xian: 枚举到参数类型:java.lang.Integer
2020-03-08 19:58:29.982 16600-16600/com.xxt.xtest D/xian: 枚举到参数类型:java.lang.String

4、Method 获取返回值类型

获取指定成员函数返回值类型的方法,同样根据返回值是否是泛型,同样有两种方法:

1
2
Class<?> getReturnType()
Type getGenericReturnType()

示例代码:

1
2
3
4
Class<?> clazz = Person.class;
Method method = clazz.getDeclaredMethod("testInvoke", Integer.class, String.class);
Class type = method.getReturnType();
Log.d(TAG, "返回值类型为:" + type.getName());

结果为:

1
2020-03-08 20:01:26.430 16600-16600/com.xxt.xtest D/xian: 返回值类型为:java.lang.Boolean

四、实战实例讲解


1、Fragment.instantiate()

在 V4 包中,Fragment 有一个函数,可以根据指定 Fragment 的路径名来得到对应 Fragment 的实例。它的完整声明如下:

1
public static Fragment instantiate(Context context, String fname, Bundle args)

  • context:Context 类型对象;
  • fname:对应 Fragment 类的完整的路径名,可以利用 Class.getName() 得到。
  • args:是在构造 Fragment 实例时需要的 Bundle参数。

使用方法
新建一个Fragment:

1
2
3
4
5
6
7
import androidx.fragment.app.Fragment;
public class DemoFragment extends Fragment {
public void printFragment(String name){
Log.d("xian", name + "知道你是个逗逼");
}
}

然后我利用 Fragment.instantiate 来获取它的实例:

1
2
3
Class<?> clazz = DemoFragment.class;
DemoFragment fragment = (DemoFragment) Fragment.instantiate(this, clazz.getName(), null);
fragment.printFragment("先小涛");

结果为:

1
2020-03-08 20:10:23.552 17684-17684/com.xxt.xtest D/xian: 先小涛知道你是个逗逼

原理
这个函数内部是如何实现的吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
try {
// 根据 fname 得到对应 Fragment 的 Class 对象
Class<?> clazz = sClassMap.get(fname);
// 首先通过一个 sClassMap 中查找一下。如果没有,则通过
// ClassLoader 将其加载到内存中,并将其保存到 sClassMap中。
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(fname);
sClassMap.put(fname, clazz);
}
// 得到对应 Fragment 的实例
Fragment f = (Fragment) clazz.getConstructor().newInstance();
if (args != null) {
// 将传入的 Bundle 参数 args 加入到 Fragment 的 mArguments参数对象中
args.setClassLoader(f.getClass().getClassLoader());
f.setArguments(args);
}
// 将 Fragment 的实例 f 返回
return f;
} catch (ClassNotFoundException e) {
...
}
...
}

2、PopupWindow.setTouchModal()

如果我想要一个效果:点击 PopupWindow 外部区域,弹窗不消失,但是点击事件会向下面的 activity 传递,这时候就需要用到 PopupWindow 的 setTouchModal(boolean touchModal) 函数。该函数完整声明为:

1
2
3
4
5
6
7
8
/**
* Set whether this window is touch modal or if outside touches will be sent to
* other windows behind it.
* @hide
*/
public void setTouchModal(boolean touchModal) {
mNotTouchModal = !touchModal;
}

在注解中,利用 @hide 将此函数隐藏了,我们通过 PopupWindow对象是没有办法得到调用它的。

所以我们只有通过反射来做。下面我们来举个例子来调用这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void setPopupWindowTouchModal (PopupWindow popupWindow,
boolean touchModal) {
if (null == popupWindow) {
return;
}
try {
Method method = PopupWindow.class.getDeclaredMethod(
"setTouchModal", boolean.class);
method.setAccessible(true);
method.invoke(popupWindow, touchModal);
} catch (Exception e) {
System.out.println(e);
}
}

原文链接:
夯实JAVA基本之二 —— 反射(3):类内部信息获取